![]() |
![]() |
|
Eine Klasse ist nicht nur auf die Implementierung einer Schnittstelle beschränkt, es dürfen – im Gegensatz zur Vererbung – auch mehrere sein. Wird die Klasse außerdem noch aus einer anderen Klasse abgeleitet oder implementiert die Klasse mehrere Schnittstellen, werden alle Typbezeichner durch ein Komma getrennt aufgelistet:
Schnittstellen dürfen nach der Veröffentlichung nicht mehr verändert werden, da sowohl der Client als auch die implementierende Klasse in einem Vertragsverhältnis zueinander stehen und die Bedingungen des Vertrags erfüllt werden müssen. Im wirklichen Leben ist das auch nicht anders. Mit der Veröffentlichung einer Schnittstelle erklärt sich eine Klasse bereit, die Schnittstelle exakt so zu implementieren, wie sie entworfen wurde. Die von der Klasse übernommenen Mitglieder der Schnittstelle müssen daher in jeder Hinsicht identisch zu ihrer Definition sein:
Ein aus einer Schnittstelle übernommenes Mitglied darf nur public sein. Es ist zulässig, in einer Klasse ein Schnittstellenmitglied abstract oder virtual zu implementieren, es darf jedoch nicht static oder const sein. Das folgende Codefragment zeigt die Klasse Hubschrauber, welche die oben definierte Schnittstelle ILuftfahrzeug implementiert und konventionsgemäß alle Schnittstellenmitglieder veröffentlicht:
Schnittstellen und VererbungVererbung ist eines der Kernkonzepte objektorientierter Systeme. Eine Klasse, die aus einer anderen Klasse abgeleitet wird, erbt alle Methoden der Basisklasse. Wir wissen auch, dass Ereignisse aus der Basisklasse nicht an die abgeleitete Klasse vererbt werden. Daher stellt sich die Frage, ob ein Interface gleichermaßen ein nicht vererbbares Feature darstellt oder die abgeleitete Klasse die aus einer Schnittstelle übernommenen Methoden der Basisklasse veröffentlicht. Wir wollen das an einem kleinen Beispiel prüfen.
Die Schnittstelle IMyInterface definiert die Methode Proc, die von jeder implementierenden Klasse übernommen werden muss – im Code oben ist es die Klasse ClassA. ClassB wird aus ClassA abgeleitet. Wenn Sie dieses sehr einfache Programm starten, wird es fehlerfrei ausgeführt und beweist damit, dass auch die Schnittstellenmethoden vererbt werden. Hinsichtlich einer Schnittstelle zeigt eine Methode polymorphes Verhalten. Das setzt sich jedoch nicht bei den ableitenden Klassen durch. Eine ableitende Klasse kann daher die implementierte Methode nur erben oder mit new verdecken. Soll sich die Methode auch in den ableitenden Klassen polymorph verhalten, muss sie mit dem Modifizierer virtual signiert werden. Mehrdeutigkeiten mit expliziter Implementierung vermeidenImplementiert eine Klasse mehrere Schnittstellen, kann es passieren, dass in zwei oder mehr Schnittstellen ein gleichnamiges Mitglied definiert ist. Diese Mehrdeutigkeit wird durch die explizite Implementierung eines Schnittstellenmembers aus der Welt geschafft. Eine explizite Implementierung ist der vollständig kennzeichnende Name eines Schnittstellenmitglieds, bestehend aus dem Namen der Schnittstelle und dem Bezeichner des implementierten Mitglieds, getrennt durch einen Punkt. Nehmen wir an, in den beiden Schnittstellen ICopy und IDocument wäre jeweils eine Methode Copy definiert:
In einer Klasse ClassA, die beide Schnittstelle veröffentlicht, könnten die Methoden folgendermaßen explizit implementiert werden, um sie eindeutig den Schnittstellen zuzuordnen:
Es müssen nicht zwangsläufig beide Methoden explizit implementiert werden. Um eine eindeutige Schnittstellenzuordnung zu gewährleisten, würde eine explizite Implementierung vollkommen ausreichen. Explizit implementierte Methoden haben keinen Zugriffsmodifizierer, denn im Zusammenhang mit der expliziten Schnittstellenimplementierung ist eine wichtige Regel zu beachten:
Auf die explizite Implementierung eines Schnittstellenmembers kann nur über eine Schnittstelleninstanz zugegriffen werden, der die Referenz auf das konkrete Objekt zugewiesen wird, dessen Typdefinition das Schnittstellenmember explizit implementiert.
Einer Objektvariablen kann nur dann eine Referenz zugewiesen werden, wenn sich beide in einer Vererbungsbeziehung befinden, sich eine Klasse somit aus einer anderen ableitet. Eine implementierte Schnittstelle wird wie eine Basisklasse behandelt. Deshalb kann einer Schnittstellenreferenz die Referenz auf ein Objekt vom Typ der implementierenden Klasse zugewiesen werden. Es kommt zu einer impliziten Konvertierung:
Im Anschluss daran wird auf die Schnittstellenreferenz die Methode Copy aufgerufen:
Eine in einer Schnittstelle definierte Methode ist, wie auch eine abstrakte Methode, implizit virtuell. Die Bindung erfolgt dynamisch zur Laufzeit, der Aufruf ist polymorph und wird an die Methode des tatsächlichen Typs weitergeleitet. An der Konsole erscheint die Ausgabe
Explizite Implementierungen von Schnittstellenmitgliedern heben Mehrdeutigkeiten bei Schnittstellenmitgliedern mit derselben Signatur auf. Ohne explizite Implementierung könnte eine Klasse keine gleichnamigen Schnittstellenmethoden mit derselben Signatur haben. Es gibt noch einen zweiten Grund, sich für die explizite Implementierung zu entscheiden: Über einen Objektverweis kann nicht auf explizite Implementierungen zugegriffen werden. Es ist so, als wäre das implementierte Schnittstellenmitglied private definiert, der direkte Zugriff ist nicht möglich. Schnittstellen, die selbst Schnittstellen implementierenMehrere Schnittstellen können zu einer neuen Schnittstelle zusammengefasst werden. Das folgende Codefragment zeigt, wie die Schnittstelle ICollect die beiden Schnittstellen IInterface1 und IInterface2 beerbt.
Eine Klasse, die sich die Dienste der Schnittstellen IInterface1, IInterface2 und ICollect sichern möchte, braucht dazu nur die Schnittstelle ICollect zu implementieren:
Hat eine Klasse eine bestimmte Schnittstelle implementiert?In der täglichen Programmierpraxis werden Sie immer wieder auf dieselben Schwierigkeiten stoßen und Lösungen entwerfen müssen. Eine der aufgeworfenen Fragen wird lauten: Wie kann ich feststellen, ob der Typ eines Objekts eine bestimmte Schnittstelle implementiert? Betrachten wir dazu ein einfaches Beispiel und stellen uns vor, dass in der Anwendung CircleApplication die Schnittstelle IDraw definiert ist, die von den Klassen GraphicCircle und GraphicRectangle implementiert wird.
Nun soll ein Array Objekte vom Typ Circle, GraphicCircle, Rectangle und GraphicRectangle verwalten. Die Methode Draw der Schnittstelle IDraw soll auf alle Objekte ausgeführt werden, die diese Schnittstelle veröffentlichen. Dabei kann es sich nur um die Objekte vom Typ der GraphicCircle und GraphicRectangle handeln. Zur Vermeidung von Fehlern muss in einer Schleife jede Objektreferenz zuerst daraufhin geprüft werden, ob der von ihr beschriebene Typ die Schnittstelle IDraw implementiert. Der Typoperator is, der im Kontext der if-Anweisung eingesetzt wird, erfüllt diese Aufgabe:
Wir haben diesen Operator bereits im Zusammenhang mit der Typüberprüfung eingesetzt. In der folgenden Main-Methode wird ein Array mit zehn Elementen vom Typ der allen gemeinsamen Basisklasse GeometricObject deklariert. In einer Schleife wird jedem Array-Element die Referenz auf ein Objekt vom Typ Circle, GraphicCircle, Rectangle oder GraphicRectangle zugewiesen. Die überladene Methode Next der Klasse Random, die uns mit den beiden Argumenten 0 und 4 die Zufallszahlen 0, 1, 2 oder 3 liefert, bestimmt mit dem jeweiligen Rückgabewert für jedes Element den entsprechenden Typ. Ist der Rückgabewert eine 0, wird die Klasse Circle instanziiert, mit 1 die Klasse GraphicCircle, usw.
Interessant ist für uns insbesondere die zweite Schleife, in der mit
geprüft wird, ob der Typ der aktuellen Referenz die Schnittstelle IDraw implementiert. Im positiven Fall wird das Array-Alement zuerst explizit in den Typ der Schnittstelle konvertiert und darauf die Methode Draw polymorph ausgeführt:
Die Ausgabe könnte beispielsweise wie folgt lauten:
Wie Sie sehen, spielt auch hier die Polymorphie eine tragende Rolle, um die zu einem bestimmten Objekt gehörende Methode aufzurufen. 6.10.4 Typumwandlung mit dem »as«-Operator
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IDraw id = (IDraw)obj; |
C# bietet mit dem as-Operator noch eine weitere Konvertierungsvariante an:
| IDraw id = obj as IDraw; |
Das Ergebnis ist dasselbe – wenn die angegebene Instanz auch wirklich die Schnittstelle implementiert. Beide Möglichkeiten, der is- und der as-Operator, verhalten sich aber grundsätzlich unterschiedlich, wenn das Objekt die Schnittstelle nicht implementiert. Die Prüfung mit dem is-Operator löst nämlich eine Exception (Ausnahme) aus, während der as-Operator null zurückliefert. Letzterer bietet sich deshalb auch insbesondere dann an, wenn in einem if-Statement eine Schnittstellenuntersuchung vorgenommen werden soll:
| if(obj as IDraw != null) ... |
Die Notwendigkeit bzw. der Vorteil der Schnittstellenimplementierung ist am Anfang nicht einfach zu verstehen. Bestimmt werden Sie erkannt haben, dass die Schnittstellenimplementierung als eine Ergänzung des Vererbungskonzepts angesehen werden kann. Die Beispiele waren bisher alle sehr einfach gehalten, nun wollen wir uns einem komplexeren Beispiel zuwenden und dabei die Lösung für ein Problem erarbeiten, das zuerst durch eine abstrakte Klasse beschrieben wird und im zweiten Ansatz durch eine Schnittstelle.
Stellen Sie sich vor, Sie möchten einen Algorithmus implementieren, der die Objekte eines Arrays der Größe nach auf- bzw. absteigend sortiert. Der Algorithmus soll sich dabei nur auf bestimmte .NET-Typen beschränken. Eine leichte Aufgabe, werden Sie jetzt vermutlich denken. Aber aus der Einschränkung auf bestimmte Typen resultiert eine verhältnismäßig komplizierte Lösung.
Am Anfang steht die Überlegung, die Sortierroutine mit dem Bezeichner SortElements in einer eigenen Klasse zu implementieren. Der erste Ansatz könnte dann wie folgt aussehen:
| public class ArraySort { |
| public static void SortElements(IrgendEinTyp[] arr) { |
| // Anweisungen |
| } |
| } |
Der Rückgabewert von SortElements ist void, folglich wird das sortierte Array über den Parameter an den Aufrufer zurückgegeben. Der Implementierung werden wir uns gleich widmen, denn zuerst müssen wir uns Gedanken darüber machen, von welchem Typ das übergebene Array sein soll. Im Codefragment ist der Typ noch mit IrgendEinTyp angegeben.
Um die Sortierung auf bestimmte Typen einzuschränken, müssen wir diesen exakt festlegen. Dazu definieren wir eine zweite Klasse, die später als Basisklasse von den Klassen abgeleitet werden muss, deren Instanzen von SortElements sortiert werden sollen. Wir legen damit den Typ des übergebenen Arrays fest, denn nach den Regeln der Objektorientierung gilt, dass ein Objekt einer abgeleiteten Klasse auch gleichzeitig vom Typ seiner Basisklasse ist.
| public class SortableObject { |
| // Anweisungen |
| } |
Nun können wir den Methodenkopf von SortElements anpassen:
| public static void SortElements(SortableObject[] arr) |
Damit genügen wir der Forderung, nur bestimmte .NET-Typen sortieren zu können. Unabhängig davon, ob ein Array vom Typ Person, Elefant oder ClassA übergeben wird, wird der Parameter das Array mittels impliziter Konvertierung in Empfang nehmen – vorausgesetzt natürlich, dass die Klassen von SortableObject abgeleitet sind. Widmen wir uns nun der Realisierung der Methode SortElements. Es gibt verschiedene Algorithmen, um Elemente zu sortieren: Bubblesort, Quicksort, Insertionsort – um nur einige zu nennen. Die Bevorzugung eines dieser Sortierverfahren hängt vom Umfang der Daten und vom durchzuführenden Vergleich ab.
Für unser Beispiel habe ich mich für das Bubblesort-Verfahren entschieden. Der Name rührt wohl daher, dass sich die Funktionsweise sehr gut mit den aufsteigenden Luftblasen in einer Flüssigkeit vergleichen lässt. Die Elemente eines Arrays werden in aufsteigender Richtung durchlaufen, und dabei werden immer zwei benachbarte Elemente verglichen. Angenommen, ein Array namens MyArr mit vier Elementen soll der Größe nach sortiert werden, dann würden nacheinander die Elementpaare
| MyArr[0] – MyArr[1] |
| MyArr[1] – MyArr[2] |
| MyArr[2] – MyArr[3] |
verglichen und jedes Paar in die richtige Reihenfolge gebracht. Wenn das Array aufsteigend sortiert werden soll, muss das zweite Element größer als das erste sein. Die Folge ist nach diesen drei Vergleichen, dass das höchstwertige Element – selbst wenn es sich im ursprünglichen Array ganz am Anfang befindet – bis an das Ende des Arrays (MyArr[3]) durchgereicht wird. Die Anzahl der Paarvergleiche entspricht der Bedingung:
Anzahl der Array-Elemente – 1
Dieser Durchlauf wird wiederholt, wobei das bereits richtig einsortierte Element keine Berücksichtigung mehr findet:
| MyArr[0] – MyArr[1] |
| MyArr[1] – MyArr[2] |
Nach dem zweiten Durchlauf befindet sich das Element mit dem zweithöchsten Wert an der vorletzten Array-Position. Die Paarvergleiche werden so lange fortgesetzt, bis der Algorithmus mit dem letzten Paarvergleich
| MyArr[0] – MyArr[1] |
beendet wird.
Das Bubblesort-Sortierverfahren lässt sich am einfachsten mit zwei Schleifen implementieren:
| Eine äußere Schleife mit einer Anzahl von Schleifendurchläufen, die der Bedingung Anzahl der Arrayelemente – 1 genügt. |
| Eine innere Schleife, die den Paarvergleich durchführt und gegebenenfalls die Reihenfolge der benachbarten Elemente vertauscht. |
Ausschlaggebend dafür, an welcher Position sich ein Objekt im sortierten Array einreiht, ist der paarweise Objektvergleich. Es stellt sich nun allerdings die Frage, nach welchen Kriterien Objekte vom Typ SortableObject verglichen werden sollen. Die statische Methode SortElements kann darüber keine Entscheidung treffen, da sie die typspezifischen Vergleichskriterien nicht kennt. Konsequenterweise muss der Vergleich in den von SortableObject abgeleiteten Klassen erfolgen. Dazu wird den ableitenden Klassen eine Methode vorgeschrieben, die als Ergebnis des Vergleichs zweier typgleicher Objekte einen booleschen Wert liefert. Wir nennen diese Methode CompareTo.
Damit jede Klasse, die SortableObject ableitet, die Methode CompareTo nach eigenen Maßstäben implementiert, wird CompareTo in der Klasse SortableObject abstrakt definiert. Damit sieht die endgültige Klassendefinition wie folgt aus:
| public abstract class SortableObject { |
| public abstract bool CompareTo(SortableObject obj); |
| } |
Der Rückgabewert soll true sein, wenn das Objekt, auf dem die Methode CompareTo aufgerufen wird, größer ist als das Objekt, das dem Parameter übergeben wird. In allen anderen Fällen sei der Rückgabewert false. Wie und nach welchen Gesichtspunkten der Vergleich erfolgt, entscheidet die Klasse, welche die abstrakte Methode CompareTo überschreibt. Natürlich können die booleschen Rückgabewerte auch vertauscht werden. Dann werden die Array-Elemente jedoch nicht auf-, sondern absteigend sortiert.
Mit diesen Vorgaben kann nun die Methode SortElements vollständig implementiert werden:
| public class ArraySort { |
| public static void SortElements(SortableObject[] arr) { |
| // n = Anzahl der Elemente |
| int n = arr.Length; |
| // Temp = temporäre Variable |
| SortableObject Temp; |
| for(int i = n – 1; i >= 1; i--) { |
| for(int k = 0; k <= i-1; k++) { |
| if(arr[k].CompareTo(arr[k + 1])) { |
| Temp = arr[k]; |
| arr[k] = arr[k + 1]; |
| arr[k + 1] = Temp; |
| } |
| } |
| } |
| } |
| } |
Resümieren wir an dieser Stelle, denn wir haben bereits alle Anforderungen erfüllt. Wir haben die abstrakte Klasse SortableObject entwickelt, welche die Methode CompareTo bereitstellt, um zwei Objekte miteinander zu vergleichen. Per Definition kann die Methode CompareTo nur Objekte vergleichen, deren Klassen die abstrakte Klasse SortableObject ableiten.
In der Klasse ArraySort ist eine Methode implementiert, die in der Lage ist, ein Array von SortableObject-Objekten der Größe nach zu sortieren. Aber warum müssen es gerade Objekte dieses Typs sein, warum nicht andere, beliebige Objekte, von denen beispielsweise einfach zwei Längenmaße miteinander verglichen werden? Die Antwort liefert ein Blick in die Implementierung der Sortierroutine SortElements. Der Entwickler der Klasse ArraySort kannte die abstrakte Klasse SortableObject. Er wusste, dass Klassen, welche die Klasse SortableObject ableiten, die Methode CompareTo bereitstellen. Auf diese Kenntnis wird in der Sortierroutine zurückgegriffen, wenn die CompareTo-Methode auf ein Objekt aufgerufen wird.
ArraySort und SortableObject seien in einer Klassenbibliothek implementiert.
Versetzen wir uns in die Lage eines Benutzers, der eine Klasse namens LngNumber entwickelt, die unter anderem ein Feld vom Typ long bereitstellt. Dieser Benutzer möchte sicherstellen, dass ein Objekt-Array vom Typ LngNumber der Größe nach sortiert werden kann.
Um sich die Mühe einer eigenen Implementierung zu sparen, recherchiert er in diversen Dokumentationen und stößt auf die Klasse ArraySort mit ihrer Methode SortElements, welche die Lösung seines Problems darstellt. Da beide Klassen voneinander abhängig sind, werden sich beide vermutlich sogar in derselben Klassenbibliothek befinden. Der Dokumentation entnimmt unser fiktiver Benutzer außerdem, dass er die Klasse ArraySort ableiten und deren abstrakte Methode CompareTo implementieren muss. Das Ergebnis könnte in den für uns entscheidenden Punkten wie folgt aussehen:
| public class LngNumber : SortableObject { |
| private long lngValue; |
| // Konstruktor |
| public LngNumber(long lng) { |
| lngValue = lng; |
| } |
| // Eigenschaft |
| public long Value { |
| get {return lngValue;} |
| set {lngValue = value;} |
| } |
| // überschriebene Instanzmethode |
| public override bool CompareTo(SortableObject b) { |
| if(this.lngValue < ((LngNumber)b).lngValue) |
| return true; |
| return false; |
| } |
| } |
Was uns jetzt noch bleibt, ist die Bestätigung unserer Überlegungen durch eine Testanwendung:
| // ---------------------------------------------------------- |
| // Beispiel: ...\Kapitel 6\Sortierroutine1 |
| // ---------------------------------------------------------- |
| class Program { |
| static void Main(string[] args) { |
| LngNumber[] arr = new LngNumber[5]; |
| arr[0] = new LngNumber(8); |
| arr[1] = new LngNumber(6); |
| arr[2] = new LngNumber(34); |
| arr[3] = new LngNumber(232); |
| arr[4] = new LngNumber(2); |
| // Aufruf der statischen Methode SortElements unter |
| // Übergabe des Objekt-Arrays |
| ArraySort.SortElements(arr); |
| // Ausgabe an der Konsole |
| for(int i = 0; i <= 4; i++) { |
| Console.Write("Element[" + i + "]"); |
| Console.WriteLine(" = " + arr[i].Value); |
| } |
| Console.ReadLine(); |
| } |
| } |
Zunächst wird ein Array aus fünf Elementen vom Typ LngNumber deklariert, die im zweiten Schritt unter Übergabe der Initialisierungswerte an den Konstruktor konkretisiert werden. Die Array-Elemente liegen zunächst in unsortierter Reihenfolge vor und werden mit der Anweisung
| ArraySort.SortElements(arr); |
in die richtige Reihenfolge gebracht. Die Ausgabe an der Konsole wird für die Elemente des Arrays arr lauten:
| Element[0] = 2 |
| Element[1] = 6 |
| Element[2] = 8 |
| Element[3] = 34 |
| Element[4] = 232 |
Sie sehen, dass die Sortierroutine ihre Aufgabe einwandfrei erledigt. Wenigstens haben sich unsere Mühen gelohnt, wenn der Weg auch ein wenig steinig war.
Der Code des Beispiels aus dem vorhergehenden Abschnitt funktioniert tadellos. Aber ihm haftet ein wesentliches Problem an, das sehr häufig auftritt, wenn abstrakte Basisklassen abgeleitet werden: Die Common Language Runtime unterstützt keine Mehrfachvererbung, sondern erlaubt nur eine Basisklasse. Solange die Klasse LngNumber nicht aus einer anderen Basisklasse abgeleitet wird, ist der oben gezeigte Lösungsansatz akzeptabel. Sobald aber eine weitere Basisklasse ins Rampenlicht rückt, muss ein anderer Weg beschritten werden.
Genau an dieser Stelle greift das Konzept der Schnittstellen. Denn anstatt eine abstrakte Klasse zu beerben, werden die abstrakten Methoden über eine Schnittstelle offen gelegt. Damit wird ein Großteil der Funktionalität der Mehrfachvererbung wiedererlangt, ohne die damit verbundenen Nachteile in Kauf nehmen zu müssen. Da eine Klasse beliebig viele Schnittstellen implementieren darf, kann sie auch um die unterschiedlichsten Verhaltensweisen erweitert werden.
Damit wird aus der abstrakten Klasse Sortable eine Schnittstellendefinition wie im Folgenden dargestellt:
| // ---------------------------------------------------------- |
| // Beispiel: ...\Kapitel 6\Sortierroutine2 |
| // ---------------------------------------------------------- |
| public interface ISortableObject { |
| bool CompareTo(ISortableObject a); |
| } |
Konventionsgemäß ergänzen wir den Schnittstellenbezeichner um das Präfix »I«. Die Änderung einer abstrakten Klasse in eine Schnittstelle wirkt sich weder auf die Definition der Klasse ArraySort noch auf die der Methode SortElements aus. Allerdings müssen die Klasse LngNumber und die aus der Schnittstelle übernommenen Methoden an die Schnittstellenimplementierung angepasst werden. Während die Ableitung einer abstrakten Klasse das Überschreiben der abstrakten Methoden mit dem Schlüsselwort override erforderlich macht, ist dieses bei der Implementierung der Schnittstellenmember in der implementierenden Klasse nicht zulässig.
Der folgende Codeausschnitt gibt die notwendigen Änderungen wieder.
| class LngNumber : ISortableObject { |
| private long lngValue; |
| // Konstruktor |
| public LngNumber(long lng) { |
| lngValue = lng; |
| } |
| public long Value { |
| get {return lngValue;} |
| set {lngValue = value;} |
| } |
| // aus der SortableObject-Schnittstelle übernommene Methode |
| public bool CompareTo(ISortableObject b) { |
| if(this.lngValue < ((LngNumber)b).lngValue) |
| return true; |
| return false; |
| } |
| } |
Damit ist die ursprünglich abstrakte Klasse durch eine Schnittstelle ersetzt, und der Code wird in gleicher Weise zum Ziel führen. Nicht anzuzweifeln ist die durch die Schnittstellendefinition gewonnene Flexibilität im Vergleich zur abstrakten Klasse, da eine Schnittstelle das möglicherweise unumgängliche Ableiten einer Basisklasse nicht blockiert. Daher sollten Schnittstellen immer dann bevorzugt eingesetzt werden, wenn die Implementierungsvererbung nicht unbedingt notwendig ist.
| Schnittstellen sind die konsequente Fortsetzung der Idee einer abstrakten Klasse. Schnittstellen bieten einer Klasse ihre Dienste in Form von abstrakten Membern an, und der Nutzer verpflichtet sich, diese zu implementieren. |
| Interfaces können nicht instanziiert werden, weil sie nur Verhaltensweisen festlegen, jedoch keine Daten bereitstellen. |
| Eine Schnittstelle wird mit dem Schlüsselwort interface und dem sich anschließenden Bezeichner definiert. Daran folgt in geschweiften Klammern ein Block abstrakter Definitionen. Konventionsgemäß beginnt der Bezeichner einer Schnittstelle mit »I«. |
| Im Gegensatz zu einer abstrakten Klasse enthalten Schnittstellen ausschließlich abstrakte Methoden, Eigenschaften oder Indexer. |
| Eine Klasse kann beliebig viele Schnittstellen implementieren. Implementiert eine Basisklasse eine Schnittstelle, wird die Schnittstelle an alle von dieser Basisklasse abgeleiteten Subklassen weitervererbt. |
| Ein aus einer Schnittstelle übernommenes Mitglied darf nur public sein. Es ist zulässig, in einer Klasse ein Schnittstellenmitglied abstract oder virtual zu implementieren, es darf jedoch nicht static oder const sein. |
| Da eine Klasse beliebig viele Schnittstellen implementieren darf, könnte es zu Mehrdeutigkeiten kommen, wenn von zwei oder mehr Schnittstellen eine gleichnamige Methode bereitgestellt wird. Mit der expliziten Implementierung kann dennoch auf jede einzelne Methode zugegriffen werden. Es darf dabei weder ein Zugriffsmodifizierer noch einer der Modifikatoren abstract, virtual, const oder static in der Signatur angegeben werden. |
| Der is-Operator bietet die Möglichkeit, eine Klasse daraufhin zu untersuchen, ob sie eine bestimmte Schnittstelle implementiert. |
| Mit dem as-Operator lässt sich einerseits eine Typkonvertierung durchführen und andererseits auch überprüfen, ob ein Objekt eine bestimmte Schnittstelle unterstützt. Der Rückgabewert ist null, wenn das Objekt die angegebene Schnittstelle nicht implementiert. |
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.